This work was presented in 2023 American Society of Clinical Oncology (ASCO) Annual Meeting. Available: https://doi.org/10.1200/JCO.2023.41.16_suppl.4022

About Dataset

The dataset used in this study was extracted from the database of a prospective clinical trial “Evaluation of the safety and sensitivity of 68Ga-DOTATOC PET/CT for imaging NET patients” at BC Cancer – Vancouver (NCT03583528, BC Cancer Agency Research Ethics Board Approval H17-00909). A total of 375 patients were enrolled in the trial between the start date, July 11, 2018, and the data query date, May 26, 2022.

Load Packages

library(knitr)
knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE, dpi = 200)
library(kableExtra)
library(tidyverse)
options(dplyr.summarise.inform = FALSE)
library(xlsx)
library(ggforce)
library(patchwork)
library(survival)
library(survminer)
library(tab)
library(consort)

Data Import

data_raw <- read_tsv("NET DOTATOC May 26, 2022.txt", col_names = TRUE,
                     locale = locale(encoding = "UTF-16LE"))

Data Cleaning

Correct and Convert Data Format

  • Convert date entries into appropriate format
  • Fix the formatting issue where a numeric range is automatically converted into a date. For example:
    • 10-Jun6-10
    • 20-Feb2-20
    • Oct-4010-40
  • Change the type of several variables to factor
data_complete <- data_complete %>%
  rowwise() %>% 
  mutate(
    across(c(id_dob, contains("date", ignore.case = TRUE)), ~as.Date(.x, "%m/%d/%Y")),
    
    across(c(path_num_ln_res, path_n_pos_ln, path_ki67, path_mitosis,
             chartrev_path_ki67, chartrev_path_mitotic, lsrimg_num_les),
           ~ifelse(grepl(paste(month.abb, collapse = "|"), .x), 
                   (function(x) {
                     num_spl <- str_split(x, "-", simplify = TRUE)
                     for (i in 1:length(num_spl)) {
                       num_spl[i] <- ifelse(!is.na(match(num_spl[i], month.abb)),
                                            match(num_spl[i], month.abb), parse_number(num_spl[i]))
                     }
                     num_spl <- as.numeric(num_spl)
                     paste(min(num_spl), max(num_spl), sep = "-")
                   })(.x), .x)),
    
    diag_prim_site = factor(diag_prim_site,
                            levels = c("Pancreas", "Small Intestine", "Lung",
                                       "Rectum", "Stomach", "Cecum",
                                       "Appendix", "Colon", "Liver",
                                       "Other", "Unknown Primary")),
    diag_histology = factor(diag_histology,
                            levels = c("Gastroenteropancreatic Neuroendocrine Tumors functioning and non-functioning",
                                       "Sympathoadrenal System Tumors",
                                       "Medullary thyroid cancer",
                                       "Pituitary Adenoma",
                                       "Medulloblastoma",
                                       "Merkel Cell Carcinoma",
                                       "Small Cell Lung Cancer (mainly primary tumors)",
                                       "Meningioma",
                                       "Any other NET/tumor with potential for overexpression of somatostatin receptors")))

Split Data

Split the data into the following chunks according to database setup:

  • dx_info: Diagnostic information. Contains selected entries from “Patient ID Information”, “Diagnostic Information”, and “Pathology Information at Diagnosis” forms
  • blood_at_dx: “Blood Test Information at Diagnosis” form
  • symp_n_relap:“Symptoms and Relapse Information” form
  • path_chart_rv: “Pathology Chart Review” form
  • img_chart_rv: “Imaging Chart Review” form
  • qual_img: “Qualitative Image Analysis - Local” form
  • quan_img: “Quantitative Analysis - Local” form
dx_info <- data_complete %>% 
  filter(is.na(redcap_repeat_instrument)) %>% 
  select(study_id, diag_path_date:pathology_information_at_diagnosis_complete) %>% 
  discard(~all(is.na(.x))) %>%
  map_df(~.x)

blood_at_dx <- data_complete %>% 
  filter(is.na(redcap_repeat_instrument)) %>% 
  select(study_id, blood_5hiaa:blood_test_information_at_diagnosis_complete) %>% 
  discard(~all(is.na(.x))) %>%
  map_df(~.x)

symp_n_relap <- data_complete %>% 
  filter(is.na(redcap_repeat_instrument)) %>% 
  select(study_id, peptide_secretions___1:symptoms_and_relapse_information_complete) %>% 
  discard(~all(is.na(.x))) %>%
  map_df(~.x)

path_chart_rv <- data_complete %>% 
  filter(redcap_repeat_instrument == "Pathology Chart Review") %>% 
  discard(~all(is.na(.x))) %>%
  map_df(~.x)

img_chart_rv <- data_complete %>% 
  filter(redcap_repeat_instrument == "Imaging Chart Review") %>% 
  discard(~all(is.na(.x))) %>%
  map_df(~.x)

qual_img <- data_complete %>% 
  filter(redcap_repeat_instrument == "Qualitative Image Analysis - Local") %>% 
  discard(~all(is.na(.x))) %>%
  map_df(~.x)

quan_img <- data_complete %>% 
  filter(redcap_repeat_instrument == "Quantitative Analysis - Local") %>% 
  discard(~all(is.na(.x))) %>%
  map_df(~.x)

Chart Review and Data Update

Chart review was conducted when a questionable case was identified.

Any data entry errors found throughout the entire analysis were corrected in this step. (Codes are hidden here.)

Process Ki67 and Mitosis Results

Both Ki67 and mitosis results are used to determine tumour grade (in the next step). However, these data were entered into text fields and saved as strings. To convert the strings into a comparable format, two additional variables are created to capture the lower bound and upper bound of the result. Examples are as follows:

  • If a number, e.g. 3: parse number, save lower = 3 and upper = 3
  • If less than a number, e.g. <5: parse number, save lower = NA and upper = 5
  • If greater than a number, e.g. >20: parse number, save lower = 20 and upper = NA
  • If a range, e.g. 5-10: parse both numbers, save lower = 5 and upper = 10
  • If contains none, no...found/detected/observed/present, etc.: save lower = 0 and upper = 0
  • If contains not mentioned/specified or na: save lower = NA and upper = NA
dx_info <- dx_info %>% 
  rowwise() %>%
  mutate(
    path_ki67_parse_lower = case_when(grepl('not mentioned|not specified|na|n/a',
                                            path_ki67, ignore.case = TRUE) ~ -1,
                                      grepl('none|no.*found|no.*detected|no.*observed|no.*present',
                                            path_ki67, ignore.case = TRUE) ~ 0,
                                      grepl('<|less', path_ki67) ~ NA_real_,
                                      grepl('-|&|to', path_ki67) ~ as.numeric(unlist(str_extract_all(path_ki67, "\\d+\\.*\\d*")))[1],
                                      TRUE ~ as.numeric(str_extract(path_ki67, "\\d+\\.*\\d*"))),
    path_ki67_parse_upper = case_when(grepl('none|not mentioned|not specified|na|n/a',
                                            path_ki67, ignore.case = TRUE) ~ -1,
                                      grepl('none|no.*found|no.*detected|no.*observed|no.*present',
                                            path_ki67, ignore.case = TRUE) ~ 0,
                                      grepl('>|more', path_ki67) ~ NA_real_,
                                      grepl('-|&|to', path_ki67) ~ as.numeric(unlist(str_extract_all(path_ki67, "\\d+\\.*\\d*")))[2],
                                      TRUE ~ as.numeric(str_extract(path_ki67, "\\d+\\.*\\d*"))),

    path_mitosis_parse_lower = case_when(grepl('not mentioned|not specified|na|n/a',
                                               path_mitosis, ignore.case = TRUE) ~ -1,
                                         grepl('none|no.*found|no.*detected|no.*observed|no.*present',
                                               path_mitosis, ignore.case = TRUE) ~ 0,
                                         grepl('<|less', path_mitosis) ~ NA_real_,
                                         grepl('-|&|to', path_mitosis) ~ as.numeric(unlist(str_extract_all(path_mitosis, "\\d+\\.*\\d*")))[1],
                                         TRUE ~ as.numeric(str_extract(path_mitosis, "\\d+\\.*\\d*"))),
    path_mitosis_parse_upper = case_when(grepl('not mentioned|not specified|na|n/a',
                                               path_mitosis, ignore.case = TRUE) ~ -1,
                                         grepl('none|no.*found|no.*detected|no.*observed|no.*present',
                                               path_mitosis, ignore.case = TRUE) ~ 0,
                                         grepl('>|less', path_mitosis) ~ NA_real_,
                                         grepl('-|&|to', path_mitosis) ~ as.numeric(unlist(str_extract_all(path_mitosis, "\\d+\\.*\\d*")))[2],
                                         TRUE ~ as.numeric(str_extract(path_mitosis, "\\d+\\.*\\d*"))),
    across(c(path_mitosis_parse_lower, path_mitosis_parse_upper), # 10 HPF = 2.37 mm2 = 1.185 x 2 mm2
           ~ case_when(path_mitosis_unit == "/ 10 HPF" | .x == 0 ~ .x,
                       path_mitosis_unit == "/2mm2" ~ .x/1.185,
                       path_mitosis_unit == "Other" &
                         grepl('HPF', path_mitosis_unit_other, ignore.case = TRUE) ~
                         .x/(if_else(is.na(str_extract(path_mitosis_unit_other, "\\d+\\.*\\d*")), 1,
                                     as.numeric(str_extract(path_mitosis_unit_other, "\\d+\\.*\\d*")))/10),
                       path_mitosis_unit == "Other" &
                         grepl('mm2', path_mitosis_unit_other, ignore.case = TRUE) ~
                         (.x/(if_else(is.na(str_extract(path_mitosis_unit_other, "\\d+\\.*\\d*(?=\\s?mm2)")), 1,
                                      as.numeric(str_extract(path_mitosis_unit_other, "\\d+\\.*\\d*(?=\\s?mm2)")))/2))/1.185))) %>% 
  ungroup()

Determine Tumour Grade

Determine the tumour grade based on Ki67 and mitosis results as per WHO grading scheme:
Cancer grade based on Mitosis and Ki67 test results
(Source: https://www.researchgate.net/figure/Pancreatic-Neuroendocrine-Tumors-Grading-Scheme-According-to-World-Health-Organization_tbl1_317127151)

Ki67

All results in unit %. The following table further expands the WHO grading scheme into criteria based on the values of lower and upper variables.
upper
[0 , 3) 3 (3 , 20) 20 (20 , 100 NA
lower [0 , 3) 1 1 1-2 1-2 1-3 1-3
3 X 2 2 2 2-3 2-3
(3 , 20) X X 2 2 2-3 2-3
20 X X X 2 3 3
(20 , 100 X X X X 3 3
NA 1 1 1-2 1-2 1-3 NA

Mitosis

Available units: /10 HPF, /50 HPF, /HPF, /mm2, /2 mm2
All results are converted into unit /10 HPF with conversion rate 10 HPF = 2.37 mm2 = 1.185 x 2 mm2.
If a result shows "... low ...", grade 1 is assigned even though both lower and upper are NA.
upper
[0 , 2) 2 (2 , 20) 20 (20 , 100 NA
lower [0 , 2) 1 1 1-2 1-2 1-3 1-3
2 X 2 2 2 2-3 2-3
(2 , 20) X X 2 2 2-3 2-3
20 X X X 2 3 3
(20 , 100 X X X X 3 3
NA 1 1 1-2 1-2 1-3 NA


The above criteria are implemented here. The final tumor grade is primarily determined by the Ki67 or mitosis result, whichever is higher. If no test result is available, the original pathological grade is used.

dx_info <- dx_info %>% 
  mutate(
    grade_ki67 = case_when(path_ki67_parse_lower == -1 | path_ki67_parse_upper == -1 ~ NA_character_,
                           (is.na(path_ki67_parse_lower) | path_ki67_parse_lower<3) &
                             path_ki67_parse_upper<=3 ~ "1",
                           (is.na(path_ki67_parse_lower) | path_ki67_parse_lower<3) &
                             (path_ki67_parse_upper>3 & path_ki67_parse_upper<=20) ~ "1-2",
                           (path_ki67_parse_lower>=3 & path_ki67_parse_lower<=20) & 
                             (path_ki67_parse_upper>=3 & path_ki67_parse_upper<=20) ~ "2",
                           (path_ki67_parse_lower>=3 & path_ki67_parse_lower<20) & 
                             (is.na(path_ki67_parse_upper)|path_ki67_parse_upper>20) ~ "2-3",
                           path_ki67_parse_lower>=20 &
                             (is.na(path_ki67_parse_upper)|path_ki67_parse_upper>20)~ "3",
                           path_ki67_parse_lower<3 &
                             (is.na(path_ki67_parse_upper)|path_ki67_parse_upper>20) ~ "1-3",
                           is.na(path_ki67_parse_lower) & path_ki67_parse_upper>20 ~ "1-3"),
    grade_mitosis = case_when(path_mitosis_parse_lower == -1 | path_mitosis_parse_upper == -1 ~ NA_character_,
                              (is.na(path_mitosis_parse_lower) | path_mitosis_parse_lower<2) &
                                path_mitosis_parse_upper<=2 ~ "1",
                              (is.na(path_mitosis_parse_lower) | path_mitosis_parse_lower<2) &
                                (path_mitosis_parse_upper>2 & path_mitosis_parse_upper<=20) ~ "1-2",
                              (path_mitosis_parse_lower>=2 & path_mitosis_parse_lower<=20) & 
                                (path_mitosis_parse_upper>=2 & path_mitosis_parse_upper<=20) ~ "2",
                              (path_mitosis_parse_lower>=2 & path_mitosis_parse_lower<20) & 
                                (is.na(path_mitosis_parse_upper)|path_mitosis_parse_upper>20) ~ "2-3",
                              path_mitosis_parse_lower>=20 &
                                (is.na(path_mitosis_parse_upper)|path_mitosis_parse_upper>20)~ "3",
                              path_mitosis_parse_lower<2 &
                                (is.na(path_mitosis_parse_upper)|path_mitosis_parse_upper>20) ~ "1-3",
                              is.na(path_mitosis_parse_lower) & path_mitosis_parse_upper>20 ~ "1-3",
                              is.na(path_mitosis_parse_lower) & is.na(path_mitosis_parse_upper) &
                                grepl("low|inconspicuous|rare|uncommon|only 1", path_mitosis,
                                      ignore.case = TRUE) ~ "1"),
    grade_testresult = c("1", "1-2", "2", "1-3", "2-3", "3")[pmax(match(grade_ki67,
                                                                        c("1", "1-2", "2", "1-3", "2-3", "3")),
                                                                  match(grade_mitosis,
                                                                        c("1", "1-2", "2", "1-3", "2-3", "3")),
                                                                  na.rm = TRUE)],
    grade_final = case_when(is.na(grade_testresult) & is.na(path_grade) ~ "Unknown Grade",
                            is.na(grade_testresult) & !is.na(path_grade) ~ paste0("Grade ", path_grade),
                            !is.na(grade_testresult) ~ paste0("Grade ", grade_testresult)))


If the resulting grade is indefinite (1-2, 2-3 or 1-3), chart review is conducted by an oncologist to finalize a grade.

Additional Grouping and Processing

  • Group metastatic and non-metastatic cases
  • Group NET cases and non-NET cases
  • Check Grade and Differentiation cross references
    • If Grade 1 or 2 AND Differentiation NA: update Differentiation Well Differentiated
    • If Differentiation Poorly Differentiated AND Grade NA: update Grade 3
  • Calculate age at diagnosis
dx_info <- dx_info %>% 
  left_join(img_chart_rv %>% 
              mutate(across(chartrev_image_pos_area___7:chartrev_image_pos_area___14, ~if_else(str_detect(., "Unchecked$"), 0, 1)),
                     met_status = rowSums(across(chartrev_image_pos_area___7:chartrev_image_pos_area___14))) %>% 
              group_by(study_id) %>% 
              summarize(img_met_status = if_else(sum(met_status) > 0, "Yes", "No")),
            by = "study_id") %>% 
  mutate(
    met_status_final = case_when(is.na(path_met_status) | path_met_status == "Not Specified" ~ img_met_status,
                                      path_met_status == "No" & img_met_status == "No" ~ "No",
                                      path_met_status == "Yes" | img_met_status == "Yes" ~ "Yes"),
    diag_differentiation = if_else(grade_final %in% c("Grade 1", "Grade 2") & 
                                     is.na(diag_differentiation), 
                                   "Well Differentiated", diag_differentiation),
    grade_final = if_else(diag_differentiation == "Poorly Differentiated" & 
                            is.na(grade_final), 
                          "Grade 3", grade_final),
    grade_final = case_when(grade_final == "Grade 3" & 
                              diag_differentiation == "Poorly Differentiated" ~
                              "Grade 3, Poorly Diff.",
                            grade_final == "Grade 3" & 
                              diag_differentiation == "Well Differentiated" ~
                              "Grade 3, Well Diff.",
                            grade_final == "Grade 3" & 
                              is.na(diag_differentiation) ~
                              "Grade 3, Unknown Diff.",
                            TRUE ~ grade_final) %>% 
      factor(levels = c("Grade 1", "Grade 2", "Grade 3, Well Diff.",
                        "Grade 3, Poorly Diff.", "Grade 3, Unknown Diff.",
                        "Unknown Grade")),
    hist_group = case_when(grepl('NET|Neuroendocrine', diag_histology, ignore.case = TRUE) ~ "NET",
                           grepl('Merkel', diag_histology, ignore.case = TRUE) ~ "Merkel",
                           grepl('Small Cell', diag_histology, ignore.case = TRUE) ~ "Small Cell",
                           is.na(diag_histology) ~ "NA",
                           TRUE ~ "Other") %>% 
      factor(levels = c("NET", "Merkel", "Small Cell", "Other", "NA")),
    age_at_diagnosis = as.numeric(diag_path_date - id_dob)/365.25)

Summarize DOTATOC/FDG Scan Results

  • Gather results of baseline DOTATOC/FDG scans
  • Attach Krenning scores from different sources
  • Count total number of DOTATOC/FDG scans received
  • Count total number of DOTATOC/FDG scans with positive results
krenning_LA_SSA <- read.xlsx("LA-SSA Krenning Score No. of Lesions.xlsx",
                             1, header = TRUE, check.names = FALSE) %>% 
  select(`Study ID`, `Date of Scan`, `No. of Lesions`, `Krenning Score`) %>% 
  rename(study_id = 1, qual_exam_date_LA_SSA = 2,
         num_les_LA_SSA = 3, qual_krenning_LA_SSA = 4) %>% 
  mutate(num_les_LA_SSA = if_else(grepl("no lesion", num_les_LA_SSA, ignore.case = TRUE),
                                  NA_character_, num_les_LA_SSA)) %>% 
  filter(study_id <= 375)

krenning_sarah <- read.xlsx("LA-SSA Krenning Score No. of Lesions.xlsx",
                            2, header = TRUE, check.names = FALSE) %>% 
  rename(study_id = 1, qual_krenning_sarah = 2)

krenning_KS <- read.xlsx("KS_170_256.xlsx",
                         1, header = TRUE, check.names = FALSE) %>% 
  rename(study_id = 1, qual_krenning_KS = 2) %>% 
  filter(study_id != "186B") %>% 
  mutate(study_id = as.double(study_id))

dx_info <- dx_info %>% 
  left_join(qual_img %>% 
              filter(!is.na(qual_exam_date)) %>%
              group_by(study_id) %>% 
              summarize(scan_date_dot1 = min(qual_exam_date[qual_tracer == "68Ga-DOTATOC"], na.rm = TRUE),
                        interp_date_dot1 = qual_interp_date[qual_tracer == "68Ga-DOTATOC" &
                                                              qual_exam_date == scan_date_dot1],
                        interp_name_dot1 = qual_interp_name[qual_tracer == "68Ga-DOTATOC" &
                                                              qual_exam_date == scan_date_dot1],
                        img_quality_dot1 = qual_img_quality[qual_tracer == "68Ga-DOTATOC" &
                                                              qual_exam_date == scan_date_dot1],
                        overall_assess_dot1 = qual_overall_assess[qual_tracer == "68Ga-DOTATOC" &
                                                                    qual_exam_date == scan_date_dot1],
                        certainty_dot1 = qual_diag_certainty[qual_tracer == "68Ga-DOTATOC" &
                                                               qual_exam_date == scan_date_dot1],
                        num_lesion_dot1 = lsrimg_num_les[qual_tracer == "68Ga-DOTATOC" &
                                                           qual_exam_date == scan_date_dot1],
                        local_dot1 = (qual_les_site___1[qual_tracer == "68Ga-DOTATOC" &
                                                          qual_exam_date == scan_date_dot1] == "Checked"),
                        regional_dot1 = (qual_les_site___2[qual_tracer == "68Ga-DOTATOC" &
                                                             qual_exam_date == scan_date_dot1] == "Checked"),
                        distant_bone_dot1 = (qual_les_site___3[qual_tracer == "68Ga-DOTATOC" &
                                                                 qual_exam_date == scan_date_dot1] == "Checked"),
                        distant_lung_dot1 = (qual_les_site___4[qual_tracer == "68Ga-DOTATOC" &
                                                                 qual_exam_date == scan_date_dot1] == "Checked"),
                        distant_liver_dot1 = (qual_les_site___5[qual_tracer == "68Ga-DOTATOC" &
                                                                  qual_exam_date == scan_date_dot1] == "Checked"),
                        distant_other_dot1 = (qual_les_site___98[qual_tracer == "68Ga-DOTATOC" &
                                                                   qual_exam_date == scan_date_dot1] == "Checked"),
                        distant_other_spec_dot1 = qual_les_site_other[qual_tracer == "68Ga-DOTATOC" &
                                                                        qual_exam_date == scan_date_dot1],
                        krenning_dot1 = qual_krenning[qual_tracer == "68Ga-DOTATOC" &
                                                        qual_exam_date == scan_date_dot1])) %>% 
  left_join(qual_img %>% 
              filter(!is.na(qual_exam_date)) %>%
              group_by(study_id) %>% 
              summarize(scan_date_fdg1 = min(qual_exam_date[qual_tracer == "18F-FDG"], na.rm = TRUE),
                        interp_date_fdg1 = qual_interp_date[qual_tracer == "18F-FDG" &
                                                              qual_exam_date == scan_date_fdg1],
                        interp_name_fdg1 = qual_interp_name[qual_tracer == "18F-FDG" &
                                                              qual_exam_date == scan_date_fdg1],
                        img_quality_fdg1 = qual_img_quality[qual_tracer == "18F-FDG" &
                                                              qual_exam_date == scan_date_fdg1],
                        overall_assess_fdg1 = qual_overall_assess[qual_tracer == "18F-FDG" &
                                                                    qual_exam_date == scan_date_fdg1],
                        certainty_fdg1 = qual_diag_certainty[qual_tracer == "18F-FDG" &
                                                               qual_exam_date == scan_date_fdg1],
                        num_lesion_fdg1 = lsrimg_num_les[qual_tracer == "18F-FDG" &
                                                           qual_exam_date == scan_date_fdg1],
                        local_fdg1 = (qual_les_site___1[qual_tracer == "18F-FDG" &
                                                          qual_exam_date == scan_date_fdg1] == "Checked"),
                        regional_fdg1 = (qual_les_site___2[qual_tracer == "18F-FDG" &
                                                             qual_exam_date == scan_date_fdg1] == "Checked"),
                        distant_bone_fdg1 = (qual_les_site___3[qual_tracer == "18F-FDG" &
                                                                 qual_exam_date == scan_date_fdg1] == "Checked"),
                        distant_lung_fdg1 = (qual_les_site___4[qual_tracer == "18F-FDG" &
                                                                 qual_exam_date == scan_date_fdg1] == "Checked"),
                        distant_liver_fdg1 = (qual_les_site___5[qual_tracer == "18F-FDG" &
                                                                  qual_exam_date == scan_date_fdg1] == "Checked"),
                        distant_other_fdg1 = (qual_les_site___98[qual_tracer == "18F-FDG" &
                                                                   qual_exam_date == scan_date_fdg1] == "Checked"),
                        distant_other_spec_fdg1 = qual_les_site_other[qual_tracer == "18F-FDG" &
                                                                        qual_exam_date == scan_date_fdg1])) %>% 
  left_join(krenning_LA_SSA, by = "study_id") %>% 
  left_join(krenning_sarah, by = "study_id") %>% 
  left_join(krenning_KS, by = "study_id") %>% 
  mutate(scan_date_dot1 = if_else(is.na(scan_date_dot1), qual_exam_date_LA_SSA, scan_date_dot1),
         num_lesion_dot1 = if_else(is.na(num_lesion_dot1), num_les_LA_SSA, num_lesion_dot1),
         krenning_dot1 = case_when(!is.na(krenning_dot1) ~ krenning_dot1,
                                   !is.na(qual_krenning_LA_SSA) ~ qual_krenning_LA_SSA,
                                   !is.na(qual_krenning_sarah) ~ qual_krenning_sarah,
                                   !is.na(qual_krenning_KS) ~ qual_krenning_KS,
                                   TRUE ~ krenning_dot1),
         krenning_dot1 = if_else(overall_assess_dot1 == "Negative", 0, krenning_dot1),
         overall_assess_dot1 = case_when(krenning_dot1 == 0 ~ "Negative",
                                         is.na(overall_assess_dot1) & !is.na(num_lesion_dot1) ~ "Positive",
                                         TRUE ~ overall_assess_dot1),
         
         overall_assess_dot1 = factor(overall_assess_dot1, levels = c("Negative", "Positive")),
         overall_assess_fdg1 = factor(overall_assess_fdg1, levels = c("Negative", "Positive")),

         assess_group = case_when(overall_assess_dot1 == "Positive" & overall_assess_fdg1 == "Positive" ~ "DOTATOC+ FDG+",
                                  overall_assess_dot1 == "Positive" & overall_assess_fdg1 == "Negative" ~ "DOTATOC+ FDG-",
                                  overall_assess_dot1 == "Negative" & overall_assess_fdg1 == "Positive" ~ "DOTATOC- FDG+",
                                  overall_assess_dot1 == "Negative" & overall_assess_fdg1 == "Negative" ~ "DOTATOC- FDG-") %>% 
           factor(levels = c("DOTATOC+ FDG+", "DOTATOC+ FDG-", "DOTATOC- FDG+", "DOTATOC- FDG-"))) %>% 
  select(-qual_exam_date_LA_SSA, -num_les_LA_SSA, -qual_krenning_LA_SSA,
         -qual_krenning_sarah, -qual_krenning_KS)

dx_info <- dx_info %>%
  left_join(qual_img %>% 
              filter(!is.na(qual_exam_date) | !is.na(qual_overall_assess) | !is.na(lsrimg_num_les)) %>%
              group_by(study_id) %>% 
              summarize(earliest_scan_date = min(qual_exam_date, na.rm = TRUE),
                        
                        num_dot = sum(qual_tracer == "68Ga-DOTATOC"),
                        num_dot_positive = sum(qual_tracer == "68Ga-DOTATOC" &
                                                 qual_overall_assess == "Positive"),
                        
                        num_fdg = sum(qual_tracer == "18F-FDG"),
                        num_fdg_positive = sum(qual_tracer == "18F-FDG" &
                                                 qual_overall_assess == "Positive"))) %>%
  mutate(diagnosis_to_scan_day = if_else(earliest_scan_date - diag_path_date < 0,
                                         0, as.numeric(earliest_scan_date - diag_path_date)),
         duration_dot1_fdg1 = abs(as.numeric(scan_date_dot1 - scan_date_fdg1)),
         across(c(num_dot, num_dot_positive, num_fdg, num_fdg_positive), 
                ~if_else(is.na(.x), 0L, .x)),
         pos_to_scan_ratio_dot = if_else(num_dot == 0, "No DOTATOC scan", str_c(num_dot_positive, '/', num_dot)),
         pos_to_scan_ratio_fdg = if_else(num_fdg == 0, "No FDG scan", str_c(num_fdg_positive, '/', num_fdg)),
         num_lesion_dot1 = if_else(overall_assess_dot1 == "Negative", "0", num_lesion_dot1) %>% 
           factor(levels = c("0", "1", "2", "3", "4", "5", "6-10", ">10")),
         num_lesion_fdg1 = if_else(overall_assess_fdg1 == "Negative", "0", num_lesion_fdg1) %>% 
           factor(levels = c("0", "1", "2", "3", "4", "5", "6-10", ">10"))) %>% 
  left_join(qual_img %>% 
              filter(!is.na(qual_overall_assess)) %>% 
              select(study_id, qual_tracer, qual_overall_assess) %>% 
              pivot_wider(names_from = qual_tracer,
                          values_from = qual_overall_assess,
                          values_fn = ~paste0(.x, collapse = " -> ")) %>% 
              rename(overall_assess_alldot = `68Ga-DOTATOC`,
                     overall_assess_allfdg = `18F-FDG`))

Attach Survival Data

The survival status of each patient was reviewed and updated on Jan 24, 2023.

osdata_raw <- read_csv("Dotatoc Survival (JL JP MZ 1.24.2023)_deidentified.csv", col_names = TRUE) %>% 
  rename(study_id = `Study ID`,
         last_contact = `Date of Death or Last Contact`,
         status = `Death=1`) %>% 
  mutate(study_id = parse_number(str_replace_all(study_id, "DOTATOC-01-", "")),
         last_contact = as.Date(last_contact, "%d-%b-%y"))

dx_info <- dx_info %>% 
  left_join(osdata_raw) %>% 
  mutate(survival_day = as.numeric(last_contact - diag_path_date),
         survival_year = survival_day/365.25)

Additional Filtering

net_data <- dx_info %>%
  filter(hist_group == "NET")

gepnet_data <- net_data %>% 
  filter(diag_histology == "Gastroenteropancreatic Neuroendocrine Tumors functioning and non-functioning")

net_met_data <- net_data %>% 
  filter(met_status_final == "Yes")

gepnet_met_data <- net_met_data %>% 
    filter(diag_histology == "Gastroenteropancreatic Neuroendocrine Tumors functioning and non-functioning",
           pos_to_scan_ratio_dot != "No DOTATOC scan",
           pos_to_scan_ratio_fdg != "No FDG scan")

CONSORT Diagram

Function for counting total number records in a given data frame

count_total_func <- function(df, description, new_table = FALSE) {
  summary_tb <- tibble_row(Parameter = description,
                           df %>% summarize(Total = n()))
  
  if (by_grade) {
    summary_tb <- summary_tb %>%
      bind_cols(df %>%
                  group_by(grade_final) %>%
                  summarize(n = n()) %>%
                  filter(if (na_grade_remove) grade_final != "Unknown Grade" else TRUE) %>%
                  pivot_wider(names_from = grade_final, values_from = n)) %>%
      mutate(across(contains("Grade"), ~ paste0(.x, ' (', sprintf("%.1f", .x/Total*100), ')')))
  }

  summary_tb <- summary_tb %>%
    mutate(Total = as.character(Total))

  if (new_table) pack_row_index <<- c()
  pack_row_index <<- setNames(c(pack_row_index, nrow(summary_tb)),
                              c(names(pack_row_index), " "))
  
  return(summary_tb)
}

Function for calculating the median value of a given data frame with 25 and 75 percentiles

median_func <- function(df, variable, description, new_table = FALSE) {
  summary_tb <- tibble_row(Parameter = description,
                           df %>% 
                             summarize(med = as.integer(median({{variable}}, na.rm = TRUE)),
                                       perc25 = as.integer(quantile({{variable}}, 0.25, na.rm = TRUE)),
                                       perc75 = as.integer(quantile({{variable}}, 0.75, na.rm = TRUE)))) %>% 
    mutate(Total = str_c(med, ' (', perc25, '-', perc75, ')')) %>% 
    select(-med, -perc25, -perc75)
  
  if (by_grade) {
    summary_tb <- summary_tb %>% 
      bind_cols(df %>%
                  group_by(grade_final) %>%
                  summarize(med = as.integer(median({{variable}}, na.rm = TRUE)),
                            perc25 = as.integer(quantile({{variable}}, 0.25, na.rm = TRUE)),
                            perc75 = as.integer(quantile({{variable}}, 0.75, na.rm = TRUE))) %>% 
                  mutate(med = str_c(med, ' (', perc25, '-', perc75, ')')) %>% 
                  select(-perc25, -perc75) %>% 
                  filter(if (na_grade_remove) grade_final != "Unknown Grade" else TRUE) %>%
                  pivot_wider(names_from = grade_final, values_from = med))
  }

  if (new_table) pack_row_index <<- c()
  pack_row_index <<- setNames(c(pack_row_index, nrow(summary_tb)),
                              c(names(pack_row_index), " "))
  
  return(summary_tb)
}

Function for counting number of records by group from one column in a given data frame

count_bygroup_func <- function(df, group_var, description, new_table = FALSE) {
  summary_tb <- df %>% 
    group_by({{group_var}}) %>% 
    summarize(Total = as.integer(n())) %>% 
    filter(if (na_groupvar_remove) !is.na({{group_var}}) else TRUE) %>%
    rename(Parameter = {{group_var}}) %>% 
    mutate(Total = if_else(is.na(Total), as.character(0L),
                           paste0(Total, ' (', sprintf("%.1f", Total/sum(Total)*100), ')')),
           Parameter = as.character(Parameter))
  
  if (by_grade) {
    summary_tb <- summary_tb %>% 
      full_join(df %>%
                  group_by(grade_final, {{group_var}}) %>%
                  summarize(n = n()) %>% 
                  filter(if (na_grade_remove) grade_final != "Unknown Grade" else TRUE) %>%
                  filter(if (na_groupvar_remove) !is.na({{group_var}}) else TRUE) %>%
                  rename(Parameter = {{group_var}}) %>% 
                  mutate(Parameter = as.character(Parameter)) %>% 
                  pivot_wider(names_from = grade_final, values_from = n)) %>% 
      mutate(across(contains("Grade"), ~
                      if_else(is.na(.x), as.character(0L),
                              paste0(.x, ' (', sprintf("%.1f", .x/sum(.x, na.rm = TRUE)*100), ')'))))
  }
  
  if (new_table) pack_row_index <<- c()
  pack_row_index <<- setNames(c(pack_row_index, nrow(summary_tb)),
                              c(names(pack_row_index), description))

  return(summary_tb)
}

Function for counting number of records by group from multiple columns in a given data frame

count_pack_func <- function(df_list, description_list, pack_description,
                            sum_df = NULL, new_table = FALSE) {
  summary_tb <- tibble()
  for (i in 1:length(df_list)) {
    summary_tb <- summary_tb %>%
      bind_rows(tibble(Parameter = as.character(description_list[[i]]),
                       df_list[[i]] %>% summarize(Total = n())))
  }
  
  total_num <- if_else(is.null(sum_df), as.integer(sum(summary_tb$Total)), nrow(sum_df))
  summary_tb <- summary_tb %>% 
    mutate(Total = if_else(is.na(Total), as.character(0L),
                           paste0(Total, ' (', sprintf("%.1f", Total/total_num*100), ')')))
  
  if (by_grade) {
    by_grade_tb <- tibble()
    for (i in 1:length(df_list)) {
      by_grade_tb <- by_grade_tb %>% 
        bind_rows(df_list[[i]] %>%
                    group_by(grade_final) %>%
                    summarize(n = n()) %>%
                    filter(if (na_grade_remove) grade_final != "Unknown Grade" else TRUE) %>%
                    pivot_wider(names_from = grade_final, values_from = n))
    }

    if (is.null(sum_df)) {
      total_num <- by_grade_tb %>%
        summarize(across(everything(), ~sum(.x, na.rm = TRUE)))
    } else {
      total_num <- sum_df %>%
        group_by(grade_final) %>%
        summarize(n = n()) %>%
        filter(if (na_grade_remove) grade_final != "Unknown Grade" else TRUE) %>%
        pivot_wider(names_from = grade_final, values_from = n)
    }
    
    by_grade_tb <- by_grade_tb %>% 
      mutate(across(contains("Grade"), ~
                      if_else(is.na(.x), as.character(0L),
                              paste0(.x, ' (', sprintf("%.1f", .x/total_num$.x*100), ')'))))
    
    summary_tb <- bind_cols(summary_tb, by_grade_tb)
  }
  
  if (new_table) pack_row_index <<- c()
  pack_row_index <<- setNames(c(pack_row_index, nrow(summary_tb)),
                              c(names(pack_row_index), pack_description))
  
  return(summary_tb)
}

Table 1.1: Baseline characteristics for all patients

Parameter Total Grade 1 Grade 2 Grade 3, Well Diff. Grade 3, Poorly Diff. Grade 3, Unknown Diff. Unknown Grade
Number of Subjects 375 148 (39.5) 131 (34.9) 16 (4.3) 10 (2.7) 1 (0.3) 69 (18.4)
Sex
F 187 (49.9) 72 (48.6) 66 (50.4) 7 (43.8) 3 (30.0) 0 39 (56.5)
M 188 (50.1) 76 (51.4) 65 (49.6) 9 (56.2) 7 (70.0) 1 (100.0) 30 (43.5)
Median Age at Diagnosis 59 (48-69) 59 (50-67) 60 (47-69) 65 (40-72) 65 (58-70) 72 (72-72) 45 (37-61)
Median Time from Diagnosis to Scan (Days) 475 (92-1637) 487 (92-1804) 676 (111-1644) 193 (30-692) 88 (61-618) 322 (322-322) 199 (129-2509)
Metastasis
No 147 (39.2) 57 (38.5) 33 (25.2) 2 (12.5) 2 (20.0) 0 53 (76.8)
Yes 228 (60.8) 91 (61.5) 98 (74.8) 14 (87.5) 8 (80.0) 1 (100.0) 16 (23.2)
Histology
Gastroenteropancreatic Neuroendocrine Tumors functioning and non-functioning 248 (78.7) 120 (83.9) 104 (81.9) 15 (93.8) 5 (55.6) 0 4 (21.1)
Sympathoadrenal System Tumors 16 (5.1) 6 (4.2) 5 (3.9) 1 (6.2) 0 0 4 (21.1)
Medullary thyroid cancer 13 (4.1) 0 1 (0.8) 0 1 (11.1) 0 11 (57.9)
Pituitary Adenoma 1 (0.3) 1 (0.7) 0 0 0 0 0
Merkel Cell Carcinoma 2 (0.6) 0 0 0 1 (11.1) 1 (100.0) 0
Small Cell Lung Cancer (mainly primary tumors) 1 (0.3) 0 0 0 1 (11.1) 0 0
Meningioma 1 (0.3) 1 (0.7) 0 0 0 0 0
Any other NET/tumor with potential for overexpression of somatostatin receptors 33 (10.5) 15 (10.5) 17 (13.4) 0 1 (11.1) 0 0
Histology = NA
No cancer diagnosis 54 (88.5) 5 (100.0) 2 (50.0) 0 0 0 47 (92.2)
Other type of cancer 7 (11.5) 0 2 (50.0) 0 1 (100.0) 0 4 (7.8)

Table 1.2: Baseline characteristics for metastatic GEPNET cases

Parameter Total Grade 1 Grade 2 Grade 3, Well Diff. Grade 3, Poorly Diff. Unknown Grade
Number of Subjects 165 73 (44.2) 75 (45.5) 13 (7.9) 2 (1.2) 2 (1.2)
Sex
F 78 (47.3) 32 (43.8) 38 (50.7) 5 (38.5) 2 (100.0) 1 (50.0)
M 87 (52.7) 41 (56.2) 37 (49.3) 8 (61.5) 0 1 (50.0)
Median Age at Diagnosis 64 (52-71) 61 (52-69) 65 (55-72) 65 (40-75) 70 (69-71) 51 (45-57)
Median Time from Diagnosis to Scan (Days) 665 (120-1863) 491 (120-2165) 814 (137-1822) 67 (27-665) 1086 (1065-1107) 1334 (752-1916)
Median Time Between Baseline DOTATOC and FDG Scans (Days) 4 (1-11) 4 (1-11) 4 (1-11) 5 (3-7) 20 (10-30) 12 (9-15)
Median Follow-Up Time (Days) 1473 (747-2650) 1563 (846-2983) 1564 (777-2669) 811 (495-1099) 1476 (1467-1484) 2142 (1408-2876)

Table 2.1: Clinical characteristics for metastatic GEPNET cases

Parameter Total Grade 1 Grade 2 Grade 3, Well Diff. Grade 3, Poorly Diff. Unknown Grade
Total Number of metastatic GEP NET cases 165 73 (44.2) 75 (45.5) 13 (7.9) 2 (1.2) 2 (1.2)
Primary Site
Pancreas 37 (22.4) 15 (20.5) 15 (20.0) 7 (53.8) 0 0
Small Intestine 86 (52.1) 47 (64.4) 37 (49.3) 1 (7.7) 0 1 (50.0)
Rectum 7 (4.2) 1 (1.4) 3 (4.0) 1 (7.7) 2 (100.0) 0
Stomach 2 (1.2) 0 2 (2.7) 0 0 0
Cecum 5 (3.0) 2 (2.7) 2 (2.7) 0 0 1 (50.0)
Appendix 1 (0.6) 0 1 (1.3) 0 0 0
Colon 2 (1.2) 0 2 (2.7) 0 0 0
Liver 1 (0.6) 0 0 1 (7.7) 0 0
Unknown Primary 24 (14.5) 8 (11.0) 13 (17.3) 3 (23.1) 0 0
Differentiation at Diagnosis
Poorly Differentiated 2 (1.2) 0 0 0 2 (100.0) 0
Well Differentiated 162 (98.8) 73 (100.0) 75 (100.0) 13 (100.0) 0 1 (100.0)
Number of DOTATOC-avid Scan(s) / Total Number of DOTATOC Scan(s) Received
0/1 7 (4.2) 3 (4.1) 4 (5.3) 0 0 0
1/1 145 (87.9) 65 (89.0) 64 (85.3) 12 (92.3) 2 (100.0) 2 (100.0)
1/2 1 (0.6) 0 1 (1.3) 0 0 0
2/2 10 (6.1) 4 (5.5) 5 (6.7) 1 (7.7) 0 0
4/4 2 (1.2) 1 (1.4) 1 (1.3) 0 0 0
Number of FDG-avid Scan(s) / Total Number of FDG Scan(s) Received
0/1 74 (44.8) 42 (57.5) 31 (41.3) 0 0 1 (50.0)
0/2 2 (1.2) 2 (2.7) 0 0 0 0
0/4 1 (0.6) 0 1 (1.3) 0 0 0
1/1 81 (49.1) 26 (35.6) 40 (53.3) 12 (92.3) 2 (100.0) 1 (50.0)
1/2 1 (0.6) 1 (1.4) 0 0 0 0
2/2 6 (3.6) 2 (2.7) 3 (4.0) 1 (7.7) 0 0

Table 2.2 & 2.3: Baseline DOTATOC/FDG scan results for metastatic GEPNET cases

Parameter Total Grade 1 Grade 2 Grade 3, Well Diff. Grade 3, Poorly Diff. Unknown Grade
Baseline DOTATOC - Assessment
Negative 7 (4.2) 3 (4.1) 4 (5.3) 0 0 0
Positive 158 (95.8) 70 (95.9) 71 (94.7) 13 (100.0) 2 (100.0) 2 (100.0)
Baseline DOTATOC - Number of Lesions in Positive Cases
1 11 (7.0) 10 (14.3) 1 (1.4) 0 0 0
2 11 (7.0) 4 (5.7) 7 (9.9) 0 0 0
3 7 (4.4) 4 (5.7) 3 (4.2) 0 0 0
4 4 (2.5) 3 (4.3) 1 (1.4) 0 0 0
5 9 (5.7) 3 (4.3) 4 (5.6) 1 (7.7) 1 (50.0) 0
6-10 26 (16.5) 14 (20.0) 11 (15.5) 0 0 1 (50.0)
>10 90 (57.0) 32 (45.7) 44 (62.0) 12 (92.3) 1 (50.0) 1 (50.0)
Baseline DOTATOC - Site(s) of Lesion in Positive Cases
Local Relapse 17 (10.8) 5 (7.1) 9 (12.7) 3 (23.1) 0 0
Regional Nodes 86 (54.4) 39 (55.7) 35 (49.3) 10 (76.9) 1 (50.0) 1 (50.0)
Distant Metastases (Bone) 49 (31.0) 16 (22.9) 24 (33.8) 8 (61.5) 1 (50.0) 0
Distant Metastases (Lung) 9 (5.7) 2 (2.9) 6 (8.5) 0 1 (50.0) 0
Distant Metastases (Liver) 117 (74.1) 51 (72.9) 53 (74.6) 11 (84.6) 1 (50.0) 1 (50.0)
Distant Metastases (Other) 81 (51.3) 32 (45.7) 38 (53.5) 7 (53.8) 2 (100.0) 2 (100.0)
Baseline DOTATOC - Krenning Score in Positive Cases
1 2 (1.3) 1 (1.4) 0 0 1 (50.0) 0
2 7 (4.4) 2 (2.9) 4 (5.6) 1 (7.7) 0 0
3 58 (36.7) 28 (40.0) 24 (33.8) 5 (38.5) 1 (50.0) 0
4 91 (57.6) 39 (55.7) 43 (60.6) 7 (53.8) 0 2 (100.0)
Parameter Total Grade 1 Grade 2 Grade 3, Well Diff. Grade 3, Poorly Diff. Unknown Grade
Baseline FDG - Assessment
Negative 76 (46.1) 44 (60.3) 31 (41.3) 0 0 1 (50.0)
Positive 89 (53.9) 29 (39.7) 44 (58.7) 13 (100.0) 2 (100.0) 1 (50.0)
Baseline FDG - Number of Lesions in Positive Cases
1 23 (25.8) 10 (34.5) 11 (25.0) 1 (7.7) 0 1 (100.0)
2 14 (15.7) 5 (17.2) 8 (18.2) 1 (7.7) 0 0
3 5 (5.6) 2 (6.9) 3 (6.8) 0 0 0
4 4 (4.5) 1 (3.4) 2 (4.5) 1 (7.7) 0 0
5 8 (9.0) 4 (13.8) 2 (4.5) 1 (7.7) 1 (50.0) 0
6-10 13 (14.6) 6 (20.7) 5 (11.4) 1 (7.7) 1 (50.0) 0
>10 22 (24.7) 1 (3.4) 13 (29.5) 8 (61.5) 0 0
Baseline FDG - Site(s) of Lesion in Positive Cases
Local Relapse 20 (22.5) 7 (24.1) 6 (13.6) 7 (53.8) 0 0
Regional Nodes 36 (40.4) 13 (44.8) 14 (31.8) 8 (61.5) 1 (50.0) 0
Distant Metastases (Bone) 18 (20.2) 1 (3.4) 9 (20.5) 8 (61.5) 0 0
Distant Metastases (Lung) 4 (4.5) 0 2 (4.5) 1 (7.7) 1 (50.0) 0
Distant Metastases (Liver) 47 (52.8) 13 (44.8) 25 (56.8) 8 (61.5) 1 (50.0) 0
Distant Metastases (Other) 33 (37.1) 7 (24.1) 19 (43.2) 4 (30.8) 2 (100.0) 1 (100.0)

Table 3: Combined baseline DOTATOC/FDG scan results for metastatic GEPNET cases

Parameter Total Grade 1 Grade 2 Grade 3, Well Diff. Grade 3, Poorly Diff. Unknown Grade
Baseline Scan Result
DOTATOC+ FDG+ 88 (53.3) 29 (39.7) 43 (57.3) 13 (100.0) 2 (100.0) 1 (50.0)
DOTATOC+ FDG- 70 (42.4) 41 (56.2) 28 (37.3) 0 0 1 (50.0)
DOTATOC- FDG+ 1 (0.6) 0 1 (1.3) 0 0 0
DOTATOC- FDG- 6 (3.6) 3 (4.1) 3 (4.0) 0 0 0
DOTATOC Krenning Score with FDG Negative
0 6 (7.9) 3 (6.8) 3 (9.7) 0 0 0
1 1 (1.3) 1 (2.3) 0 0 0 0
2 3 (3.9) 2 (4.5) 1 (3.2) 0 0 0
3 34 (44.7) 22 (50.0) 12 (38.7) 0 0 0
4 32 (42.1) 16 (36.4) 15 (48.4) 0 0 1 (100.0)
DOTATOC Krenning Score with FDG Positive
0 1 (1.1) 0 1 (2.3) 0 0 0
1 1 (1.1) 0 0 0 1 (50.0) 0
2 4 (4.5) 0 3 (6.8) 1 (7.7) 0 0
3 24 (27.0) 6 (20.7) 12 (27.3) 5 (38.5) 1 (50.0) 0
4 59 (66.3) 23 (79.3) 28 (63.6) 7 (53.8) 0 1 (100.0)
DOTATOC Krenning Score
FDG Scan 0 1 2 3 4
Negative 6 (85.7) 1 (50.0) 3 (42.9) 34 (58.6) 32 (35.2)
Positive 1 (14.3) 1 (50.0) 4 (57.1) 24 (41.4) 59 (64.8)

Figure 1: Combined baseline DOTATOC/FDG scan results for metastatic GEPNET cases

Table 4: Number of metastatic GEPNET cases with multiple DOTATOC and/or FDG scans

Parameter Total Grade 1 Grade 2 Grade 3, Well Diff.
DOTATOC Scan Results
Positive -> Negative 1 (7.7) 0 1 (14.3) 0
Positive -> Positive 10 (76.9) 4 (80.0) 5 (71.4) 1 (100.0)
Positive -> Positive -> Positive -> Positive 2 (15.4) 1 (20.0) 1 (14.3) 0
FDG Scan Results
Negative -> Negative 2 (20.0) 2 (40.0) 0 0
Positive -> Negative 1 (10.0) 1 (20.0) 0 0
Positive -> Positive 6 (60.0) 2 (40.0) 3 (75.0) 1 (100.0)
Positive -> Positive -> Positive 1 (10.0) 0 1 (25.0) 0
DOTATOC Scan Result FDG Scan Result n
Positive -> Negative Negative 1
Positive -> Positive Negative 1
Positive -> Positive Negative -> Negative 2
Positive -> Positive Positive 1
Positive -> Positive Positive -> Positive 6
Positive -> Positive -> Positive -> Positive Positive -> Negative 1
Positive -> Positive -> Positive -> Positive Positive -> Positive -> Positive 1

Figure 2: Timeline for metastatic GEPNET cases with multiple DOTATOC and/or FDG scans

10-year overall survival for metastatic GEPNET cases

Patients with unknown grade and poorly differentiated grade 3 tumours are excluded in the survival analysis. Cases with Krenning score of 0 and 1 are combined into one group.

osdata_gepnet_met <- gepnet_met_data %>% 
  filter(grade_final != "Unknown Grade",
         grade_final != "Grade 3, Poorly Diff.") %>% 
  droplevels() %>% 
  mutate(krenning_dot1 = if_else(krenning_dot1 %in% c(1, 0), "1 or 0",
                                 as.character(krenning_dot1)) %>% 
           factor(levels = c("4", "3", "2", "1 or 0")))


Overall Survival in Different Baseline DOTATOC Results




Overall Survival in Different Baseline FDG Results




Overall Survival in Different Baseline Scan Results




Overall Survival in Different Baseline FDG Results (Grade 1 + 2)




Overall Survival in Different Baseline FDG Results (Grade 1 only)




Overall Survival in Different Baseline FDG Results (Grade 2 only)




Overall Survival in Different DOTATOC Krenning Scores




Overall Survival in Different Tumour Grades

Cox Regression Analysis

The same population from the last step was used in multivariate analysis.
The parameters of interest included grade (Grade 1 / Grade 2 / Grade 3, Well Diff.), age (≤60 / >60), primary site (Pancreas / Small Intestine / Other), baseline DOTA PET assessment (Positive / Negative), baseline DOTA PET Krenning score (4 / 3 / 2 / 1 / 0), and baseline FDG PET assessment (Positive / Negative).
Likelihood ratio test was applied to each parameter individually first.
Parameters tentatively entered into the multivariate model if the test result showed p<0.05.
With each new parameter entered into the model, the proportional hazard assumption of the updated model and Schoenfeld residuals were checked to ensure no time-dependent coefficient existed and the proportional hazard assumption remained valid.
The variable was either removed or the model was adjusted when the p-value in the assumption check was less than 0.05.
A total of three variables were expected to be included in the model due to the number of events that occurred in the population to minimize the probability of overfitting.

multi_var_gepnet_met <- osdata_gepnet_met %>% 
  select(study_id, status, survival_year, diag_prim_site, grade_final, age_at_diagnosis,
         overall_assess_dot1, num_lesion_dot1, krenning_dot1, overall_assess_fdg1, num_lesion_fdg1) %>% 
  mutate(age_group = if_else(age_at_diagnosis > 60, ">60", "\u226460") %>% 
           factor(levels = c("\u226460", ">60")),
         prim_site_group = case_when(diag_prim_site == "Pancreas" ~ "Pancreas",
                                     diag_prim_site == "Small Intestine" ~ "Small Intestine",
                                     TRUE ~ "Other") %>% 
           factor(levels = c("Pancreas", "Small Intestine", "Other")),
         dot1_les_group = case_when(num_lesion_dot1 == "0" ~ "0 (DOTATOC Negative)",
                                    num_lesion_dot1 %in% c("1", "2", "3", "4", "5") ~ "1-5",
                                    num_lesion_dot1 == "6-10" ~ "6-10",
                                    num_lesion_dot1 == ">10" ~ ">10") %>% 
           factor(levels = c(">10", "6-10", "1-5", "0 (DOTATOC Negative)")),
         fdg1_les_group = case_when(num_lesion_fdg1 == "0" ~ "0 (FDG Negative)",
                                    num_lesion_fdg1 %in% c("1", "2", "3", "4", "5") ~"1-5",
                                    num_lesion_fdg1 == "6-10" ~ "6-10",
                                    num_lesion_fdg1 == ">10" ~ ">10") %>% 
           factor(levels = c(">10", "6-10", "1-5", "0 (FDG Negative)"))) %>% 
  droplevels()


Variables to consider (first option used as reference):

  • Grade (Grade 1 / Grade 2 / Grade 3, Well Diff.)
## Call:
## coxph(formula = Surv(survival_year, status) ~ grade_final, data = multi_var_gepnet_met)
## 
##                                  coef exp(coef) se(coef)     z        p
## grade_finalGrade 2             1.6057    4.9811   0.5096 3.151 0.001627
## grade_finalGrade 3, Well Diff. 2.2531    9.5168   0.6102 3.693 0.000222
## 
## Likelihood ratio test=18.67  on 2 df, p=8.841e-05
## n= 161, number of events= 31


  • Age (<=60 / >60)
## Call:
## coxph(formula = Surv(survival_year, status) ~ age_group, data = multi_var_gepnet_met)
## 
##                coef exp(coef) se(coef)     z     p
## age_group>60 1.0213    2.7768   0.4018 2.542 0.011
## 
## Likelihood ratio test=6.99  on 1 df, p=0.008185
## n= 161, number of events= 31


  • Primary Site (Pancreas / Small Intestine / Other)
## Call:
## coxph(formula = Surv(survival_year, status) ~ prim_site_group, 
##     data = multi_var_gepnet_met)
## 
##                                    coef exp(coef) se(coef)      z     p
## prim_site_groupSmall Intestine -0.48589   0.61515  0.44124 -1.101 0.271
## prim_site_groupOther            0.05458   1.05610  0.50297  0.109 0.914
## 
## Likelihood ratio test=2.02  on 2 df, p=0.3648
## n= 161, number of events= 31


  • Baseline DOTATOC Assessment (Positive / Negative)
## Call:
## coxph(formula = Surv(survival_year, status) ~ overall_assess_dot1, 
##     data = multi_var_gepnet_met)
## 
##                                  coef exp(coef)  se(coef)     z     p
## overall_assess_dot1Positive 1.709e+01 2.634e+07 4.095e+03 0.004 0.997
## 
## Likelihood ratio test=3.06  on 1 df, p=0.0804
## n= 161, number of events= 31


  • Baseline DOTATOC Number of Lesions (>10 / 6-10 / 1-5 / 0)
## Call:
## coxph(formula = Surv(survival_year, status) ~ dot1_les_group, 
##     data = multi_var_gepnet_met)
## 
##                                          coef  exp(coef)   se(coef)      z
## dot1_les_group6-10                 -4.318e-01  6.493e-01  6.155e-01 -0.702
## dot1_les_group1-5                  -1.068e+00  3.438e-01  6.143e-01 -1.738
## dot1_les_group0 (DOTATOC Negative) -1.735e+01  2.922e-08  4.207e+03 -0.004
##                                         p
## dot1_les_group6-10                 0.4829
## dot1_les_group1-5                  0.0822
## dot1_les_group0 (DOTATOC Negative) 0.9967
## 
## Likelihood ratio test=7.16  on 3 df, p=0.06698
## n= 161, number of events= 31


  • Baseline DOTATOC Krenning Score (4 / 3 / 2 / 1 / 0)
## Call:
## coxph(formula = Surv(survival_year, status) ~ krenning_dot1, 
##     data = multi_var_gepnet_met)
## 
##                           coef  exp(coef)   se(coef)      z     p
## krenning_dot13      -3.781e-01  6.852e-01  4.164e-01 -0.908 0.364
## krenning_dot12       1.143e-01  1.121e+00  1.029e+00  0.111 0.912
## krenning_dot11 or 0 -1.722e+01  3.318e-08  3.963e+03 -0.004 0.997
## 
## Likelihood ratio test=4.25  on 3 df, p=0.2358
## n= 161, number of events= 31


  • Baseline FDG Assessment (Positive / Negative)
## Call:
## coxph(formula = Surv(survival_year, status) ~ overall_assess_fdg1, 
##     data = multi_var_gepnet_met)
## 
##                               coef exp(coef) se(coef)     z      p
## overall_assess_fdg1Positive 1.4393    4.2178   0.4898 2.938 0.0033
## 
## Likelihood ratio test=11.4  on 1 df, p=0.0007341
## n= 161, number of events= 31


  • Baseline FDG Number of Lesions (>10 / 6-10 / 1-5 / 0)
## Call:
## coxph(formula = Surv(survival_year, status) ~ fdg1_les_group, 
##     data = multi_var_gepnet_met)
## 
##                                   coef exp(coef) se(coef)      z        p
## fdg1_les_group6-10             -0.6015    0.5480   0.6682 -0.900 0.368020
## fdg1_les_group1-5              -0.6984    0.4974   0.4282 -1.631 0.102897
## fdg1_les_group0 (FDG Negative) -1.9327    0.1448   0.5591 -3.457 0.000547
## 
## Likelihood ratio test=13.93  on 3 df, p=0.003003
## n= 161, number of events= 31



Model 1: Grade + Age + Baseline FDG Assessment

## Call:
## coxph(formula = Surv(survival_year, status) ~ grade_final + age_group + 
##     overall_assess_fdg1, data = multi_var_gepnet_met)
## 
##                                  coef exp(coef) se(coef)     z       p
## grade_finalGrade 2             1.2027    3.3292   0.5163 2.330 0.01982
## grade_finalGrade 3, Well Diff. 1.9831    7.2652   0.6350 3.123 0.00179
## age_group>60                   0.9821    2.6702   0.4382 2.241 0.02500
## overall_assess_fdg1Positive    1.0546    2.8709   0.5066 2.082 0.03737
## 
## Likelihood ratio test=29.27  on 4 df, p=6.903e-06
## n= 161, number of events= 31

Check PH assumption:

##                      chisq df    p
## grade_final         3.8789  2 0.14
## age_group           1.4136  1 0.23
## overall_assess_fdg1 0.0344  1 0.85
## GLOBAL              5.2170  4 0.27
Variable HR (95% CI) P
Grade
     Grade 1 (ref)
     Grade 2 3.329 (1.210-9.158) 0.0198
     Grade 3, Well Diff. 7.265 (2.093-25.220) 0.00179
Age
     ≤60 (ref)
     >60 2.670 (1.131-6.303) 0.0250
Baseline FDG Assessment
     Negative (ref)
     Positive 2.871 (1.064-7.749) 0.0374



Model 2: Grade + Age + Baseline FDG Number of Lesions

## Call:
## coxph(formula = Surv(survival_year, status) ~ grade_final + age_group + 
##     fdg1_les_group, data = multi_var_gepnet_met)
## 
##                                    coef exp(coef) se(coef)      z       p
## grade_finalGrade 2              1.28163   3.60251  0.55076  2.327 0.01996
## grade_finalGrade 3, Well Diff.  1.64233   5.16721  0.71555  2.295 0.02172
## age_group>60                    1.43658   4.20629  0.51416  2.794 0.00521
## fdg1_les_group6-10             -0.09468   0.90967  0.76610 -0.124 0.90165
## fdg1_les_group1-5              -1.05782   0.34721  0.58138 -1.819 0.06884
## fdg1_les_group0 (FDG Negative) -1.84617   0.15784  0.68142 -2.709 0.00674
## 
## Likelihood ratio test=33.16  on 6 df, p=9.768e-06
## n= 161, number of events= 31

Check PH assumption:

##                chisq df     p
## grade_final     5.47  2 0.065
## age_group       1.49  1 0.223
## fdg1_les_group  9.33  3 0.025
## GLOBAL          9.55  6 0.145



Model 3: Grade + Age + Baseline FDG Assessment + Baseline DOTATOC Number of Lesions

## Call:
## coxph(formula = Surv(survival_year, status) ~ grade_final + age_group + 
##     overall_assess_fdg1 + dot1_les_group, data = multi_var_gepnet_met)
## 
##                                          coef  exp(coef)   se(coef)      z
## grade_finalGrade 2                  1.205e+00  3.338e+00  5.174e-01  2.329
## grade_finalGrade 3, Well Diff.      1.909e+00  6.746e+00  6.329e-01  3.016
## age_group>60                        9.747e-01  2.650e+00  4.434e-01  2.198
## overall_assess_fdg1Positive         9.001e-01  2.460e+00  5.183e-01  1.737
## dot1_les_group6-10                 -3.226e-01  7.243e-01  6.297e-01 -0.512
## dot1_les_group1-5                  -4.921e-01  6.114e-01  6.405e-01 -0.768
## dot1_les_group0 (DOTATOC Negative) -1.634e+01  7.994e-08  4.658e+03 -0.004
##                                          p
## grade_finalGrade 2                 0.01984
## grade_finalGrade 3, Well Diff.     0.00256
## age_group>60                       0.02795
## overall_assess_fdg1Positive        0.08243
## dot1_les_group6-10                 0.60844
## dot1_les_group1-5                  0.44236
## dot1_les_group0 (DOTATOC Negative) 0.99720
## 
## Likelihood ratio test=31.05  on 7 df, p=6.086e-05
## n= 161, number of events= 31

Check PH assumption:

##                      chisq df    p
## grade_final         3.9456  2 0.14
## age_group           1.6109  1 0.20
## overall_assess_fdg1 0.0513  1 0.82
## dot1_les_group      3.3560  3 0.34
## GLOBAL              8.9848  7 0.25
Variable HR (95% CI) P
Grade
     Grade 1 (ref)
     Grade 2 3.338 (1.211-9.202) 0.0198
     Grade 3, Well Diff. 6.746 (1.951-23.325) 0.00256
Age
     ≤60 (ref)
     >60 2.650 (1.111-6.321) 0.0280
Baseline FDG Assessment
     Negative (ref)
     Positive 2.460 (0.891-6.793) 0.0824
Baseline DOTATOC Number of Lesions
     >10 (ref)
     6-10 0.724 (0.211-2.488) 0.608
     1-5 0.611 (0.174-2.145) 0.442
     0 (DOTATOC Negative) 0.000 (0.000-Inf) 0.997



Model 4: Grade + Age + Baseline DOTATOC Number of Lesions

## Call:
## coxph(formula = Surv(survival_year, status) ~ grade_final + age_group + 
##     dot1_les_group, data = multi_var_gepnet_met)
## 
##                                          coef  exp(coef)   se(coef)      z
## grade_finalGrade 2                  1.263e+00  3.536e+00  5.223e-01  2.418
## grade_finalGrade 3, Well Diff.      2.151e+00  8.594e+00  6.289e-01  3.421
## age_group>60                        9.741e-01  2.649e+00  4.474e-01  2.177
## dot1_les_group6-10                 -3.911e-01  6.763e-01  6.256e-01 -0.625
## dot1_les_group1-5                  -7.760e-01  4.603e-01  6.277e-01 -1.236
## dot1_les_group0 (DOTATOC Negative) -1.680e+01  5.055e-08  4.714e+03 -0.004
##                                           p
## grade_finalGrade 2                 0.015591
## grade_finalGrade 3, Well Diff.     0.000625
## age_group>60                       0.029445
## dot1_les_group6-10                 0.531903
## dot1_les_group1-5                  0.216413
## dot1_les_group0 (DOTATOC Negative) 0.997156
## 
## Likelihood ratio test=27.56  on 6 df, p=0.0001137
## n= 161, number of events= 31

Check PH assumption:

##                chisq df    p
## grade_final     4.16  2 0.12
## age_group       1.77  1 0.18
## dot1_les_group  3.94  3 0.27
## GLOBAL         10.18  6 0.12
Variable HR (95% CI) P
Grade
     Grade 1 (ref)
     Grade 2 3.536 (1.270-9.841) 0.0156
     Grade 3, Well Diff. 8.594 (2.506-29.477)
Age
     ≤60 (ref)
     >60 2.649 (1.102-6.366) 0.0294
Baseline DOTATOC Number of Lesions
     >10 (ref)
     6-10 0.676 (0.198-2.305) 0.532
     1-5 0.460 (0.134-1.575) 0.216
     0 (DOTATOC Negative) 0.000 (0.000-Inf) 0.997



Model 5: Grade + Baseline FDG Assessment + Baseline DOTATOC Number of Lesions

## Call:
## coxph(formula = Surv(survival_year, status) ~ grade_final + overall_assess_fdg1 + 
##     dot1_les_group, data = multi_var_gepnet_met)
## 
##                                          coef  exp(coef)   se(coef)      z
## grade_finalGrade 2                  1.407e+00  4.085e+00  5.118e-01  2.749
## grade_finalGrade 3, Well Diff.      1.763e+00  5.828e+00  6.266e-01  2.813
## overall_assess_fdg1Positive         8.971e-01  2.452e+00  5.227e-01  1.716
## dot1_les_group6-10                 -1.928e-01  8.246e-01  6.288e-01 -0.307
## dot1_les_group1-5                  -3.908e-01  6.765e-01  6.436e-01 -0.607
## dot1_les_group0 (DOTATOC Negative) -1.669e+01  5.646e-08  4.434e+03 -0.004
##                                          p
## grade_finalGrade 2                 0.00597
## grade_finalGrade 3, Well Diff.     0.00491
## overall_assess_fdg1Positive        0.08615
## dot1_les_group6-10                 0.75908
## dot1_les_group1-5                  0.54372
## dot1_les_group0 (DOTATOC Negative) 0.99700
## 
## Likelihood ratio test=25.72  on 6 df, p=0.0002515
## n= 161, number of events= 31

Check PH assumption:

##                     chisq df     p
## grade_final         4.641  2 0.098
## overall_assess_fdg1 0.116  1 0.733
## dot1_les_group      4.277  3 0.233
## GLOBAL              8.586  6 0.198
Variable HR (95% CI) P
Grade
     Grade 1 (ref)
     Grade 2 4.085 (1.498-11.139) 0.00597
     Grade 3, Well Diff. 5.828 (1.707-19.901) 0.00491
Baseline FDG Assessment
     Negative (ref)
     Positive 2.452 (0.880-6.832) 0.0862
Baseline DOTATOC Number of Lesions
     >10 (ref)
     6-10 0.825 (0.240-2.828) 0.759
     1-5 0.677 (0.192-2.389) 0.544
     0 (DOTATOC Negative) 0.000 (0.000-Inf) 0.997



The final multivariate model consisted of grade, age, and baseline FDG assessment (Model 1).